package de.hsowl.tod4a.server;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

import javax.security.auth.login.LoginException;

import org.apache.log4j.Logger;
import org.jdom.JDOMException;

import de.hsowl.tod4a.global.exception.ObjectDatabaseException;
import de.hsowl.tod4a.global.http.HttpRequest;
import de.hsowl.tod4a.global.http.HttpResponse;
import de.hsowl.tod4a.global.listener.DataChangedListener;
import de.hsowl.tod4a.global.logging.Log;
import de.hsowl.tod4a.global.logging.LoggingFactory;
import de.hsowl.tod4a.global.model.ServerProperty;
import de.hsowl.tod4a.global.util.Utils;
import de.hsowl.tod4a.global.variables.DatabaseStructure;
import de.hsowl.tod4a.global.variables.XmlTags;
import de.hsowl.tod4a.server.http.HttpHandler;
import de.hsowl.tod4a.server.http.HttpServer;
import de.hsowl.tod4a.server.logging.Log4jHelper;

/**
 * Basic management Server
 * 
 * @author Sebastian Witt
 * 
 */
public class ObjectServerImpl implements ObjectServer {
	private ObjectManagerImpl objectManager;
	private HttpServer server;
	private Logger logger;
	private Log consoleLogger = LoggingFactory.getLogger(ObjectServerImpl.class);
	public final static int VERSION = 1;
	/**
	 * The path to the database without the database root.
	 */
	public File pathToDb;
	/**
	 * The root of the database.
	 */
	public File dbPath;
	/**
	 * The class file.
	 */
	public static File dbClasses;
	public static File dbUsers;
	public static File dbLog;
	public static File dbGraph;
	public static File dbTransactionId;
	public static ServerProperty prop;
	private ObjectGraph og;
	private ArrayList<DataChangedListener> listenerList = new ArrayList<DataChangedListener>();

	public ObjectServerImpl(final File pathToDb) throws ObjectDatabaseException {
		this(pathToDb, null);
	}

	public ObjectServerImpl(final File pathToDb, ServerProperty property) throws ObjectDatabaseException {
		try {
			buildHome(pathToDb);
			initLogger(pathToDb);

			if ((og = ObjectGraph.load(dbGraph)) == null) {
				og = new ObjectGraph(dbGraph);
			}

			TransactionManager.resetTransactionId();
			TransactionManager.setTransactionFile(dbTransactionId);

			logger = Log4jHelper.getLogger(this.getClass().getName());
			prop = startServer(property);

			this.objectManager = new ObjectManagerImpl(dbPath, dbClasses, dbUsers, og, listenerList, prop);
		} catch (Exception e) {
			throw new ObjectDatabaseException("Error during initialisation", e);
		}
	}

	/**
	 * Generates the basic database structure and sets the file names.
	 * 
	 * @param pathToDB
	 * @throws Exception
	 */
	private void buildHome(final File pathToDB) throws Exception {
		if (pathToDB.exists()) {
			dbPath = Utils.mergeFiles(pathToDB, DatabaseStructure.NAME);
			if (dbPath.exists() == false) {
				consoleLogger.information("Creating: " + dbPath.getAbsolutePath());
				if (dbPath.mkdir() == false) {
					throw new RuntimeException("Cant create DB Home");
				}
			}
			dbClasses = Utils.mergeFiles(dbPath, DatabaseStructure.CLASSES);
			if (dbClasses.exists() == false) {
				consoleLogger.information("Creating: " + dbClasses.getAbsolutePath());
				dbClasses.createNewFile();
				if (writeFile(dbClasses, DatabaseStructure.DEFAULT_CLASSES) == false) {
					dbClasses.delete();
				}
			}
			dbUsers = Utils.mergeFiles(dbPath, DatabaseStructure.DB_USERS);
			if (dbUsers.exists() == false) {
				consoleLogger.information("Creating: " + dbUsers.getAbsolutePath());
				dbUsers.createNewFile();
				if (writeFile(dbUsers, DatabaseStructure.DEFAULT_DB_USERS) == false) {
					dbUsers.delete();
				}
			}
			dbLog = Utils.mergeFiles(dbPath, DatabaseStructure.LOG_FILE);
			if (dbLog.exists() == false) {
				consoleLogger.information("Creating: " + dbLog.getAbsolutePath());
				dbLog.createNewFile();
			}
			dbGraph = Utils.mergeFiles(dbPath, DatabaseStructure.GRAPH);
			if (dbGraph.exists() == false) {
				consoleLogger.information("Creating: " + dbGraph.getAbsolutePath());
				dbGraph.createNewFile();
			}
			dbTransactionId = Utils.mergeFiles(dbPath, DatabaseStructure.TRANSACTION_ID);
			if (dbTransactionId.exists() == false) {
				consoleLogger.information("Creating: " + dbTransactionId.getAbsolutePath());
				if (dbTransactionId.createNewFile()) {
					TransactionManager.setTransactionFile(dbTransactionId);
					TransactionManager.saveId(0);
				}
			}
		}
	}

	/**
	 * Writes a message to a file
	 * 
	 * @param f
	 * @param content
	 * @return
	 * @throws IOException
	 * @throws JDOMException
	 * @throws Exception
	 */
	private boolean writeFile(final File f, final String message) {
		try {
			if (f.exists()) {
				FileWriter fw = new FileWriter(f);
				fw.write(message);
				fw.flush();
				fw.close();
				return true;
			}
		} catch (Exception e) {
		}
		return false;
	}

	/**
	 * Initializes the logger and sets the log file
	 * 
	 * @param pathToDb
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	private void initLogger(final File pathToDb) throws IOException, FileNotFoundException {
		Log4jHelper.configureLog4j(Utils.mergeFiles(pathToDb, DatabaseStructure.NAME + File.separator
				+ DatabaseStructure.LOG_FILE));
	}

	/**
	 * Starting a HTTP-Server.
	 * 
	 * @param prop
	 * @return
	 */
	private ServerProperty startServer(final ServerProperty prop) {
		ServerProperty p = prop;
		if (p == null) {
			p = ServerProperty.getDefaultProperty();
		}
		try {
			if (p.START_SERVER == true) {
				logger.debug("Starting server @ port " + p.PORT);

				final ServerProperty property = p;
				server = new HttpServer(p);
				server.setHandler(new HttpHandler() {
					@Override
					public void handle(HttpRequest request, HttpResponse response) throws Exception {
						ObjectManagerImpl manager = new ObjectManagerImpl(dbPath, dbClasses, dbUsers, og, listenerList, property);
						if (manager.checkLoginCredentials(request.USER, request.PASSWORD)) {
							try {
								String xmlMessage = request.getCONTENT();

								String target = xmlMessage.split(">")[0];

								// Warten bis alle Transaktionen beendet sind
								while (ObjectManagerImpl.begin == true) {
									try {
										Thread.sleep(5);
									} catch (Exception ex) {
									}
								}

								String ret = "empty";
								if (target.contains(XmlTags.STORE)) {
									manager.store(xmlMessage);
								} else if (target.contains(XmlTags.QUERY_BY_CLASS)) {
									ret = manager.getByClass(xmlMessage);
								} else if (target.contains(XmlTags.REMOVE)) {
									manager.remove(xmlMessage);
								} else if (target.contains(XmlTags.ADDUSER)) {
									manager.addUser(xmlMessage);
								} else if (target.contains(XmlTags.REMOVE_USER)) {
									manager.removeUser(xmlMessage);
								}

								response.setContent(ret, HttpResponse.STATUS_OK);
							} catch (Exception e) {
								response.setContent(XmlTags.EXCEPTION + ": " + e.getMessage(), HttpResponse.STATUS_BAD);
							}
						} else {
							consoleLogger.error("Authentification failed: User: " + request.USER + " Pw: " + request.PASSWORD);
							throw new LoginException("Wrong login credentails");
						}
					}
				});
				server.start();
			}
		} catch (Exception e) {
			logger.debug("Error", e);
		}
		return p;
	}

	/**
	 * Closes a server
	 */
	public void close() {
		try {
			if (server != null) {
				server.close();
				server.setSoTimeout(5);
			}
		} catch (Exception e) {
			// logger.error("", e);
		}
	}

	public ObjectManagerImpl getObjectManager() {
		return objectManager;
	}

	public void setObjectManager(ObjectManagerImpl objectManager) {
		this.objectManager = objectManager;
	}

	public void addDataChangedListener(DataChangedListener listener) {
		listenerList.add(listener);
	}

	public HttpServer getServer() {
		return server;
	}

	public void setServer(HttpServer server) {
		this.server = server;
	}

	public Logger getLogger() {
		return logger;
	}

	public void setLogger(Logger logger) {
		this.logger = logger;
	}

	public File getPathToDb() {
		return pathToDb;
	}

	public void setPathToDb(File pathToDb) {
		this.pathToDb = pathToDb;
	}

	public File getDbPath() {
		return dbPath;
	}

	public void setDbPath(File dbPath) {
		this.dbPath = dbPath;
	}

	public static File getDbClasses() {
		return dbClasses;
	}

	public static void setDbClasses(File dbClasses) {
		ObjectServerImpl.dbClasses = dbClasses;
	}

	public static File getDbUsers() {
		return dbUsers;
	}

	public static void setDbUsers(File dbUsers) {
		ObjectServerImpl.dbUsers = dbUsers;
	}

	public static File getDbLog() {
		return dbLog;
	}

	public static void setDbLog(File dbLog) {
		ObjectServerImpl.dbLog = dbLog;
	}

	public static File getDbGraph() {
		return dbGraph;
	}

	public static void setDbGraph(File dbGraph) {
		ObjectServerImpl.dbGraph = dbGraph;
	}

	public static File getDbTransactionId() {
		return dbTransactionId;
	}

	public static void setDbTransactionId(File dbTransactionId) {
		ObjectServerImpl.dbTransactionId = dbTransactionId;
	}

	public static ServerProperty getProp() {
		return prop;
	}

	public static void setProp(ServerProperty prop) {
		ObjectServerImpl.prop = prop;
	}

	public ObjectGraph getOg() {
		return og;
	}

	public void setOg(ObjectGraph og) {
		this.og = og;
	}

	public ArrayList<DataChangedListener> getListenerList() {
		return listenerList;
	}

	public void setListenerList(ArrayList<DataChangedListener> listenerList) {
		this.listenerList = listenerList;
	}

	public int getVersion() {
		return VERSION;
	}

}
